package tawai;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;

/**
 * The main class of the application<br>
 * Responsible for: <ol>
 * <li>Movements</li>
 * <li>Physics</li>
 * <li>Graphics</li>
 * </ol>
 *
 * @author Alex
 */
public class TawaiMain extends GameCanvas implements CommandListener{

    /**
     * Graphics object used to draw on this Canvas
     */
    private Graphics gr;

    /**
     * Associated TawaiGameDesign object<br>
     * Contains information about Layers and Sprites
     */
    private TawaiGameDesign design;

    /**
     * LayerManager responsible for drawing gamefield
     */
    private LayerManager lm;

    /**
     * Human player
     */
    private Player human;

    /**
     * Counter for alive NPCs
     */
    private int npcCount;

    /**
     * Determines whether game is up or someone have won already
     */
    private boolean gameRuns;

    /**
     * Determines whether human player won or not
     */
    private boolean humanWon;

    /**
     * Global timer is responsible for updating gamefield<br>
     * to allow movement, shooting, etc.<br>
     * Global Redraw Loop
     */
    private Timer globalTimer;

    /**
     * Vector with intercative objects on scene
     */
    private Vector activeObjects;

    /**
     * Global Loop iteration delay
     */
    private int globalRefreshRate = 50; // ms

    /**
     * Determines whether the game is paused or not
     */
    private boolean paused;
    
    /**
     * Number of currently pressed movement keys
     */
    private int pressedMoveKeys;

    /* Fonts and texte colors used in game window */
    private static final Font GAME_NOTIFY_FONT = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD,  Font.SIZE_MEDIUM);
    private static final int GAME_NOTIFY_COLOR = 0xff0000;
    private static final Font GAME_EXCLAM_FONT = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD,  Font.SIZE_LARGE);
    private static final int GAME_EXCLAM_COLOR = 0xffff00;

    /**
     * Zones of map where viewport,
     * keeping human player in center, could exceed map edges
     */
    private int rightNoScrollZone;
    private int leftNoScrollZone;
    private int bottomNoScrollZone;
    private int topNoScrollZone;

    /**
     * Viewport coordinates
     */
    private int viewportX;
    private int viewportY;

    /**
     * Commands displayed in game window
     */
    private Command restartCommand;
    private Command yesRestartCommand;
    private Command resumeCommand;

    /**
     * Constructor<br>
     * @throws IOException Thrown when problems
     * with getting objects from GameDesign occur
     */
   public TawaiMain () throws IOException {

        super(false);
        setCommandListener(this);
        restartCommand = new Command("Restart/Pause", Command.OK, 1);
        yesRestartCommand = new Command("Restart", Command.SCREEN, 1);
        resumeCommand = new Command("Resume", Command.BACK, 1);
        design = new TawaiGameDesign();
        gr = getGraphics();
        viewportX = 0;
        viewportY = 0;
    }

   /**
    * Method initializes scene and places players
    */
   public void initialize() throws IOException {

       // Saving landscape dimensions
       bottomNoScrollZone = TawaiMIDlet.getMain().getGameDesign().getObstaclesLayer().getHeight() 
               - this.getHeight()/2
               + TawaiMIDlet.getMain().getGameDesign().getObstaclesLayer().getCellHeight();
       rightNoScrollZone = TawaiMIDlet.getMain().getGameDesign().getObstaclesLayer().getWidth() 
               - this.getWidth()/2
               /*+ TawaiMIDlet.getMain().getGameDesign().getObstaclesLayer().getCellWidth()*/;
       leftNoScrollZone = getWidth()/2
               /*- TawaiMIDlet.getMain().getGameDesign().getObstaclesLayer().getCellWidth()*/;
       topNoScrollZone = getHeight()/2
               - TawaiMIDlet.getMain().getGameDesign().getObstaclesLayer().getCellHeight();

       // Initializing LayerManager
       lm = new LayerManager();
       lm.append(design.getObstaclesLayer());
       lm.append(design.getLandscapeLayer());
       lm.setViewWindow(0, 0, getWidth(), getWidth());

       // Initializing active objects Vector
       if (activeObjects == null)
            activeObjects = new Vector();
       else
           activeObjects.removeAllElements();

       // Initializing global timer
       if (globalTimer == null)
           globalTimer = new Timer();
       else if (!paused)
            globalTimer.cancel();

       // Setting game states
       paused = true; // Game is initially paused
       gameRuns = true;
       pressedMoveKeys = 0;

       // Adjusting commands
       addCommand(restartCommand);
       removeCommand(yesRestartCommand);
       removeCommand(resumeCommand);

       //Adding players
       npcCount = 0;
       addPlayers();

       // Initially adjust viewport
       viewportX = human.getVehicle().getX();
       viewportY = human.getVehicle().getY();

       if (viewportX < leftNoScrollZone)
           viewportX = leftNoScrollZone;
       if (viewportX > rightNoScrollZone)
           viewportX = rightNoScrollZone;

       if (viewportY < topNoScrollZone)
           viewportY = topNoScrollZone;
       if (viewportY > bottomNoScrollZone)
           viewportY = bottomNoScrollZone;

       viewportX -= getWidth()/2;
       viewportY -= getHeight()/2;
       lm.setViewWindow(viewportX, viewportY, this.getWidth(), this.getHeight());

       // Initially draw everything
       lm.paint(gr, 0, 0);
       gr.setFont(GAME_NOTIFY_FONT);
       gr.setColor(GAME_EXCLAM_COLOR);
       gr.drawString("Hostiles:" + Integer.toString(npcCount), 5, 5, Graphics.TOP | Graphics.LEFT);

       // Writing notification
       gr.setFont(GAME_NOTIFY_FONT);
       gr.setColor(GAME_NOTIFY_COLOR);
       gr.drawString("Press any movement", getWidth()/2, getHeight()/2, Graphics.BASELINE | Graphics.HCENTER);
       gr.drawString("key to start", getWidth()/2, GAME_NOTIFY_FONT.getHeight() + getHeight()/2 + 3, Graphics.BASELINE | Graphics.HCENTER);

       repaint();
   }

   /**
    * Create new NPC on specified position with specified direction
    * 
    * @param x New NPC's x-coordinate
    * @param y New NPC's y-coordinate
    * @param dir New NPC's direction
    * @throws IOException Thrown when problems with getting sprite occur
    */
   private void spawnNPC(int x, int y, int dir) throws IOException {

       // Creating new NPC variable and setting right value
       NPC newNPC = new NPC();
       newNPC.setVehicle(new Tank(x, y, dir, new Sprite(design.getRedTankSprite()),newNPC));

       // This is done to make NPC's tank visible
       lm.insert(newNPC.getVehicle().getSprite(),0);

       // Adding NPC to active objects Vector because AI is iteratively active
       activeObjects.addElement(newNPC);

       // This is to animate tank
       activeObjects.addElement(newNPC.getVehicle());
       
       npcCount++;
   }

   /**
    * Parses TankLayoutLayer and places tanks in specified positions
    */
   private void addPlayers() throws IOException {

       // Tanks layout is done on TankLayoutLayer
       TiledLayer layout = TawaiMIDlet.getMain().getGameDesign().getTankLayoutLayer();

       // Traversing layer while searching for tanks
       // Tile with index 1 = Human Player's Tank
       // Tile with index 2 = NPC's tank
       for(int row = 0; row < layout.getRows(); row++)
           for(int col = 0; col < layout.getColumns(); col++)
               if (layout.getCell(col, row) == 1) {// Human's tank
                  human = new Player();
                  human.setVehicle(new Tank(col * layout.getCellWidth(), row * layout.getCellHeight(), 90, new Sprite(design.getTankSprite()), human));
                  lm.insert(human.getVehicle().getSprite(),0);
                  activeObjects.addElement(human.getVehicle());
               } else if (layout.getCell(col, row) == 2) // NPC's tank
                   spawnNPC(col * layout.getCellWidth(), row * layout.getCellHeight(), 90);
   }

   /**
    * Called when game should be paused
    */
   public void pause() {

     globalTimer.cancel();
     paused = true;
   }

   /**
    * Called when the game should be started or resumed from pause
    */
   public void start() {

       globalTimer = new Timer();
       globalTimer.schedule(new GlobalTimerTask(), 0, globalRefreshRate);
       paused = false;
   }

    /**
     * Method processes key press events
     *
     * @param keyCode Code of the key being pressed
     */
    protected void keyPressed(int keyCode) {
        super.keyPressed(keyCode);

        // This is done to get correct key code
        keyCode = getGameAction(keyCode);
        if (keyCode == GameCanvas.UP || keyCode == GameCanvas.DOWN ||
                keyCode == GameCanvas.LEFT || keyCode == GameCanvas.RIGHT) {
            pressedMoveKeys++;

            if (paused)
                start();

            switch (keyCode) {
                case (GameCanvas.LEFT):
                    ((Tank)human.getVehicle()).setDir(180);
                    break;
                case (GameCanvas.RIGHT):
                    ((Tank)human.getVehicle()).setDir(0);
                    break;
                case (GameCanvas.UP):
                    ((Tank)human.getVehicle()).setDir(90);
                    break;
                case (GameCanvas.DOWN):
                    ((Tank)human.getVehicle()).setDir(270);
                    break;
                default:
            }
            
            human.getVehicle().startMoving();
            
         } else {
            if (keyCode == GameCanvas.FIRE) 
                human.getVehicle().shoot();
         }
    }

    /**
     * Method processes key released events
     * 
     * @param keyCode Code of the key being depressed
     */
    protected void keyReleased(int keyCode) {
        super.keyReleased(keyCode);

        keyCode = getGameAction(keyCode);

        // Cancel movement if move keys were depressed
        if (keyCode == GameCanvas.UP || keyCode == GameCanvas.DOWN ||
                keyCode == GameCanvas.LEFT || keyCode == GameCanvas.RIGHT) {
            pressedMoveKeys--;
            if (pressedMoveKeys == 0) 
                human.getVehicle().stopMoving();
        }
    }

    /**
     * Command listener for canvas commands
     * 
     * @param c {@link Command} to be processed
     * @param d {@link Displayable} which invoked the command
     */
    public void commandAction(Command c, Displayable d)  {

        if (d == this) {
            if (c == restartCommand) {
                // Pausing game
                // Suggesting Resume or Restart
                pause();
                gr.setFont(GAME_NOTIFY_FONT);
                gr.setColor(GAME_NOTIFY_COLOR);
                gr.drawString("Restart or resume?", getWidth()/2, getHeight()/2 - 20, Graphics.BASELINE | Graphics.HCENTER);
                removeCommand(restartCommand);
                addCommand(yesRestartCommand);
                addCommand(resumeCommand);
            } else if (c == yesRestartCommand) {
                // Yes restart
                addCommand(restartCommand);
                removeCommand(yesRestartCommand);
                removeCommand(resumeCommand);
                try {
                    initialize();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            } else if (c == resumeCommand) {
                // No restart
                addCommand(restartCommand);
                removeCommand(yesRestartCommand);
                removeCommand(resumeCommand);
                start();
            }


        }
    }

    /**
     * 
     */
    private class GlobalTimerTask extends TimerTask {

        private Object active;

        /**
         * 
         */
        public void run() {

            // Main game loop.
            for(int index = activeObjects.size() - 1; index >= 0; index--) {
                active =  activeObjects.elementAt(index);
                if (active instanceof Sprite) {
                    ((Sprite) active).nextFrame();
                    continue;
                }
                if (active instanceof IterativelyActive)
                    ((IterativelyActive)active).performIterativeAction();
            }

            adjustViewport();

            // Painting sprites and layers
            lm.paint(gr, 0, 0);
            gr.setFont(GAME_NOTIFY_FONT);
            gr.setColor(GAME_EXCLAM_COLOR);
            gr.drawString("Hostiles:" + Integer.toString(npcCount), 5, 5, Graphics.TOP | Graphics.LEFT);

            // Game state tests
            if (gameRuns) {
                if (!human.isAlive())
                    gameOver();
                if (npcCount == 0)
                    humanWon();
            } else {
                gr.setFont(GAME_EXCLAM_FONT);
                gr.setColor(GAME_EXCLAM_COLOR);
                if (humanWon) 
                    gr.drawString("HUMAN WINS", gr.getClipWidth()/2, gr.getClipHeight()/2, Graphics.BASELINE | Graphics.HCENTER);
                else
                    gr.drawString("AI WINS", gr.getClipWidth()/2, gr.getClipHeight()/2, Graphics.BASELINE | Graphics.HCENTER);
            }

            repaint();
        }

    }

    /**
     * Adjust viewport so that human player is kept in the center of the screen
     */
    private void adjustViewport () {

        // Temporarily saving human player's coordinates
        int px = human.getVehicle().getX();
        int py = human.getVehicle().getY();

        // Checking whether human is in zone, where viewport would meet world's edge
        // Do not adjust viewport in such zones
        if (px > leftNoScrollZone && px < rightNoScrollZone)
            viewportX = px - this.getWidth() / 2;

        if (py > topNoScrollZone && py < bottomNoScrollZone)
            viewportY = py - this.getHeight() / 2;

        // Set viewport of LayerManager
        lm.setViewWindow(viewportX, viewportY, this.getWidth(), this.getHeight());
    }

    /**
     * Getter for associated game design object
     *
     * @return This game's {@link TawaiGameDesign} object
     */
    public TawaiGameDesign getGameDesign() {

        return design;
    }

    /**
     * Called by object willing to get remvoed from scene
     * 
     * @param subject Object to remove from the scene
     */
    public void removeSceneObject(Object subject) {

        // If NPC asked to remove it, implies it was killed
        if (subject instanceof NPC)
            npcCount--;

        // Removing object, if it was contained
        if (activeObjects.contains(subject)) {
            activeObjects.removeElement(subject);
            if (subject instanceof Drawable)
                lm.remove(((Drawable) subject).getSprite()); // Removing sprite associated with removed object
        }

    }

  
    /**
     * Called when new scene object should be added to scene
     * 
     * @param subject New active object to be added to scene
     */
    public void addSceneObject(Object subject) {

        // Adding object if it was not added yet
        if (!activeObjects.contains(subject))  {
            activeObjects.addElement(subject);
            if (subject instanceof Sprite)
                lm.insert((Sprite) subject, 0); // Adding associated sprite to LayerManager
            if (subject instanceof Drawable)
                lm.insert(((Drawable) subject).getSprite(), 0);
        }

    }

   
    /**
     * Called when human player is killed
     */
    private void gameOver() {
        
        gameRuns = false;
        humanWon = false;
        removeCommand(restartCommand);
        addCommand(yesRestartCommand);
    }

    /**
     * Called when human player wins
     */
    private void humanWon() {

        gameRuns = false;
        humanWon = true;
        removeCommand(restartCommand);
        addCommand(yesRestartCommand);
    }

    /**
     * Checks whether specified sprite collides with any {@link Drawable}s in activeObjects vector
     * @param subject Sprite to check collisions with
     * @return {@link Vector} of {@link Drawable} objects which collide with <code>subject</code>
     */
    public Vector checkCollisions (Sprite subject) {

        Vector collidees = new Vector();
        for(int index = 0; index < activeObjects.size(); index++)
            if (activeObjects.elementAt(index) instanceof Drawable && 
            subject.collidesWith(((Drawable) activeObjects.elementAt(index)).getSprite(), false))
                collidees.addElement(activeObjects.elementAt(index));

        return collidees;
    }

}
